home *** CD-ROM | disk | FTP | other *** search
/ Ian & Stuart's Australian Mac 1 / Ian and Stuart's One (Australia).iso / Australasian Legends / Commercial / Rainbow Hill / MacDOS™ 2.0.0 / filters / filter projects / common pipe sources / pipe.c next >
Text File  |  1994-05-26  |  18KB  |  569 lines

  1. /*    pipe.c        Pipe handling
  2.  *
  3.  *    Released MacDOS 1.0.1a on 94/01/31
  4.  *    94/03/31    File created
  5.  *    94/05/26    First version completed
  6.  *
  7.  *--------------------------------------------------------------------------------------------------
  8.  *  Copyright © 1994 by Rainbow Hill Pty Ltd.
  9.  *
  10.  *    This program is free software; you can redistribute it and/or modify it under the terms of
  11.  *    the GNU General Public License as published by the Free Software Foundation; either version 2
  12.  *    of the License, or any later version.
  13.  *
  14.  *    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  15.  *    without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  16.  *    See the GNU General Public License for more details.
  17.  *
  18.  *    You should have received a copy of the GNU General Public License along with this program;
  19.  *    if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20.  */
  21.  
  22. #include <Errors.h>
  23. #include <GestaltEqu.h>
  24. #include <Processes.h>
  25. #include <PPCToolBox.h>
  26. #include <Events.h>
  27. #include "pipe.h"
  28.  
  29. /*
  30.  *    Before doing anything else, we hide from MacDOS what should only be used within filter
  31.  *    applications and from non-MacDOS applications what should only be used by MacDOS.
  32.  *    Very defensive!
  33.  */
  34. #ifdef __MacDOS__
  35. #    define onlyMDOS
  36. #    define notMDOS    static
  37. #else
  38. #    define onlyMDOS    static
  39. #    define notMDOS
  40. #    endif
  41.  
  42. /*-------------------------------------------------------- type, constant, and macros definitions */
  43.  
  44. #define closedPort        0        /* because PPC port numbers are always > 0 */
  45.  
  46. /*
  47.  *    StringAppend_M appends a P-string to another. It appears a bit more complicated than necessary
  48.  *    because it avoids referencing the arguments more than once.
  49.  */
  50. #define StringAppend_M(dst, src) {                                                            \
  51.     unsigned char    *ds0, *ds, *sr;                                                            \
  52.     short    le;                                                                                \
  53.     ds0 = (dst);                        /* point to the beginning of dst                */    \
  54.     sr = (src);                            /* point to the beginning of src                */    \
  55.     ds = ds0 + *ds0 + 1;                /* point to immediately after the end of dst    */    \
  56.     le = *sr++;                            /* get the length of src and skip it            */    \
  57.     *ds0 += le;                            /* update the length of dst                        */    \
  58.     while (le-- > 0) *ds++ = *sr++;        /* append the content of src to dst                */    \
  59.     }
  60.  
  61. /*
  62.  *    StringCopy_M copies the contents of one P-string to another P-string. Note that it refers
  63.  *    to the arguments only once.
  64.  */
  65. #define StringCopy_M(dst, src) {        \
  66.     unsigned char    *ds, *sr;            \
  67.     short    le;                            \
  68.     ds = (dst);                            \
  69.     sr = (src);                            \
  70.     le = sr[0] + 1;                        \
  71.     while (le-- > 0) *ds++ = *sr++;        \
  72.     }
  73.  
  74. /*------------------------------------------------------------------- static variable definitions */
  75.  
  76. static pipeParmsFun_t    *processParms = nil;    /* call back function to save filter parameters */
  77. static PPCPortRefNum    port = closedPort;        /* PPC port number */
  78. static unsigned char    nextFilterID;            /* ID of the next filter downstream */
  79.  
  80. /*------------------------------------------------------------------ static function declarations */
  81.  
  82. static OSErr ProcessMess(pipeMessType_t messType, unsigned char *buff);
  83. static OSErr StartOneSession(pipe_t *p);
  84.  
  85. /*---------------------------------------------------------------------------- exported variables */
  86.  
  87. onlyMDOS Str32            pipeName = "";                /* name of the filter application/process */
  88. onlyMDOS pipe_t            pipeIn = { pipeNoSession };    /* incoming pipe */
  89. onlyMDOS pipe_t            pipeOut = { pipeNoSession };/* outgoing pipe */
  90. onlyMDOS unsigned char    pipeCurrentSeq = 0;            /* last sequence number received from upstream */
  91. onlyMDOS unsigned char    pipeFilterID = pipeMacDOSID;/* ID of this filter */
  92.  
  93. /**************************************************************************** PipeAcceptInSession */
  94. onlyMDOS OSErr PipeAcceptInSession(void) {
  95.     OSErr                    retVal;
  96.     static PPCInformPBRec    informPB;    /* static: we might return before completing PPCInform */
  97.     Boolean                    userCancelled;
  98.  
  99.     /* check that the session is not already started */
  100.     if (pipeIn.session != pipeNoSession) {
  101.         retVal = noErr;
  102.         }
  103.     else {
  104.  
  105.         /* set up the parameter block to automatically accept sessions from local processes */
  106.         informPB.ioCompletion = nil;
  107.         informPB.portRefNum = port;
  108.         informPB.autoAccept = true;
  109.         informPB.portName = nil;
  110.         informPB.locationName = nil;
  111.         informPB.userName = nil;
  112.  
  113.         /* accept any session (hopefully it will be from either MacDOS or a proper filter!) */
  114.         retVal = PPCInform(&informPB, true);
  115.         if (retVal == noErr) {
  116.             PipeWaitOnCond_M(informPB.ioResult == pipePpcNotDone, userCancelled);
  117.             if (userCancelled) {
  118.                 retVal = userCanceledErr;
  119.                 }
  120.             else {
  121.  
  122.                 /* extract and process the information from the message */
  123.                 retVal = informPB.ioResult;
  124.                 if (retVal == noErr) {
  125.                     pipeFilterID = informPB.userData;    /* automatic conversion: long -> char */
  126.  
  127.                     /* set up the incoming pipe */
  128.                     pipeIn.session = informPB.sessRefNum;
  129.                     pipeCurrentSeq = 0;
  130.                     retVal = StartOneSession(&pipeIn);
  131.                     }
  132.                 }
  133.             }
  134.         }
  135.  
  136.     return (retVal);
  137.     } /* PipeAcceptInSession */
  138.  
  139. /********************************************************************************** PipeCheckQuit */
  140. Boolean PipeCheckQuit(void) {
  141.     EventRecord        keyboardPollEvent;
  142.  
  143.     return (
  144.         (
  145.                 WaitNextEvent(keyDownMask, &keyboardPollEvent, 0L, nil)
  146.                 &&
  147.                 PipeIsBreak_M(keyboardPollEvent)
  148.             )
  149.             ? true
  150.             : false
  151.         );
  152.     } /* PipeCheckQuit */
  153.  
  154. /************************************************************************************ PipeCleanUp */
  155. onlyMDOS void PipeCleanUp(void) {
  156.     PPCClosePBRec    closePB;
  157.  
  158.     /* check that the port is open */
  159.     if (port != closedPort) {
  160.  
  161.         /* close the port */
  162.         closePB.ioCompletion = nil;
  163.         closePB.portRefNum = port;
  164.         (void)PPCClose(&closePB, false);
  165.  
  166.         /* in any case, reset the port number (better safe than sorry!) */
  167.         port = closedPort;
  168.  
  169.         }
  170.  
  171.     /* Unnecessary, but it requires little effort. Let's keep everything tidy. */
  172.     pipeIn.session = pipeNoSession;
  173.     pipeOut.session = pipeNoSession;
  174.  
  175.     } /* PipeCleanUp */
  176.  
  177. /*************************************************************************************** PipeInit */
  178. notMDOS void PipeInit(
  179.                 pipeParmsFun_t    *pFun        /* --> call back function to save filter parameters */
  180.                 ) {
  181.     OSErr    err;
  182.     long    ppcAttributes;
  183.  
  184.     /* install the function to handle the filter parameters */
  185.     processParms = pFun;
  186.  
  187.     /* check that the PPC toolbox is available */
  188.     err = Gestalt(gestaltPPCToolboxAttr, &ppcAttributes);
  189.     if (err == noErr) {
  190.  
  191.         /* check whether the PPC was initialised and if not, do it */
  192.         if ((ppcAttributes & gestaltPPCSupportsRealTime) == 0) err = PPCInit();
  193.         if (err == noErr) {
  194.  
  195.             /* open the PPC port */
  196.             err = PipeOpen();
  197.             if (err == noErr) {
  198.  
  199.                 /* wait for the process upstream to start a session */
  200.                 err = PipeAcceptInSession();
  201.                 }
  202.             }
  203.         }
  204.  
  205.     /*
  206.      *    If an error occurs at this stage, we can only quit the filter.
  207.      *    In order to keep the ExitToShell call in a single place, we force the quit by
  208.      *    by attempting to report an error to MacDOS. That function will fail because
  209.      *    it will find the incoming pipe closed.
  210.      */
  211.     if (err != noErr) {
  212.         PipeReportError(err, nil);
  213.  
  214.         /* you will never come back here, because PipeReportError never returns */
  215.         }
  216.     } /* PipeInit */
  217.  
  218. /*************************************************************************************** PipeOpen */
  219. onlyMDOS OSErr PipeOpen(void) {
  220.     OSErr                retVal;
  221.     PPCOpenPBRec        openPB;
  222.     PPCPortRec            portRec;
  223.     LocationNameRec        locRec;
  224.     short                k;
  225.     /* To obtain the file name of this filter application, so that we ...  */
  226.     short                apRefNum;        /* ... can name this port like ... */
  227.     Handle                apParam;        /* ... the application file.       */
  228.  
  229.     /* check that the port is not already open */
  230.     if (port != closedPort) {
  231.         retVal = pipeDoubleInitErr;
  232.         }
  233.     else {
  234.  
  235.         /* initialise the port name structure with the name of the filter application */
  236.         GetAppParms(pipeName, &apRefNum, &apParam);
  237.         portRec.nameScript    = *(short *)*(GetString(pipeScriptStrNum));
  238.         StringCopy_M(portRec.name, pipeName);
  239.         portRec.portKindSelector = ppcByString;
  240.         StringCopy_M(portRec.u.portTypeStr, pipeType);
  241.  
  242.         /* initialise the location structure to point to the local Mac */
  243.         locRec.locationKindSelector = ppcNoLocation;
  244.  
  245.         /* initialise the parameter block to open the port */
  246.         openPB.ioCompletion = nil;
  247.         openPB.serviceType = ppcServiceRealTime;
  248.         openPB.resFlag = 0;
  249.         openPB.portName = &portRec;
  250.         openPB.locationName = &locRec;
  251.         openPB.networkVisible = false;
  252.         openPB.portRefNum = closedPort;    /* a bit of an overkill... */
  253.  
  254.         /* open the port and save its value in the global variable */
  255.         retVal = PPCOpen(&openPB, false);
  256.         if (retVal == noErr) {
  257.             port = openPB.portRefNum;
  258.             pipeIn.session = pipeNoSession;
  259.             pipeOut.session = pipeNoSession;
  260.             }
  261.         }
  262.  
  263.     return (retVal);
  264.     } /* PipeOpen */
  265.  
  266. /*************************************************************************************** PipePoll */
  267. notMDOS void PipePoll(
  268.                 unsigned char    *inBuff        /* --> pointer to the message buffer (a P-string) */
  269.                 ) {
  270.     OSErr            err;
  271.     pipe_t            *pip;
  272.     long            userData;
  273.     unsigned char    messType;
  274.     unsigned char    srcFilter;
  275.     unsigned char    dstFilter;
  276.     unsigned char    seq;
  277.     Boolean            fromUp;
  278.  
  279.     /* poll the pipes as long as no error occurs */
  280.     do {
  281.  
  282.         /* do one poll */
  283.         err = PipePollOnce(&pip, &userData, inBuff);
  284.         if (err == noErr) {
  285.  
  286.             /* Determine whether the message is from upstream. We will need it later. */
  287.             fromUp = (pip == &pipeIn) ? true : false;
  288.  
  289.             /* We have received a message. Break down the userData field into its components. */
  290.             PipeDecUserData_M(userData, messType, srcFilter, dstFilter, seq);
  291.  
  292.             /* check whether the message is for us */
  293.             if (dstFilter != pipeFilterID) {
  294.  
  295.                 /* The message is for somebody else. Forward it to the other pipe. */
  296.                 err = PipeStartWrite((fromUp) ? &pipeOut : &pipeIn, userData, inBuff);
  297.                 }
  298.             else {
  299.  
  300.                 /* The message is for us. Ensure that it is not an error message. */
  301.                 if (messType == pipeErrMess) {
  302.  
  303.                     /* An error message addressed to us? Filters cannot accept error messages. */
  304.                     err = pipeFilterRespErr;
  305.                     }
  306.                 else if (fromUp == false) {
  307.  
  308.                     /* A message from downstream addressed to us? This is not right for filters. */
  309.                     err = pipeUpstreamErr;
  310.                     }
  311.                 else {
  312.  
  313.                     /* This is a proper message from upstream. Process it. */
  314.                     pipeCurrentSeq = seq;
  315.                     err = ProcessMess(messType, inBuff);    /* err == pipeReturnE: data message */
  316.  
  317.                     } /* we did receive what we can handle */
  318.                 } /* the message was for us */
  319.             } /* PipePollOnce was successful */
  320.         } while (err == noErr  ||  err == pipePpcNotDone);
  321.  
  322.     /*
  323.      *    If we arrive here, err contains an error code. Return successfully only if we broke out
  324.      *    of the loop because we received a proper data message. Otherwise, try to report the error
  325.      *    to MacDOS but only if the error concerns the outgoing pipe. If the error comes from the
  326.      *    incoming pipe, there is not much point in attempting to send a message to it.
  327.      *
  328.      *    In any case, we want to call PipeReportError because we don't want to use ExitToShell
  329.      *    in more than one place.
  330.      */
  331.     if (err != pipeReturnE) {
  332.         if (fromUp) pipeIn.session = pipeNoSession;
  333.         PipeReportError(err, nil);
  334.  
  335.         /* you will never come back here, because PipeReportError never returns */
  336.         }
  337.     } /* PipePoll */
  338.  
  339. /*********************************************************************************** PipePollOnce */
  340. onlyMDOS OSErr PipePollOnce(pipe_t **pip, long *userData, unsigned char *buff) {
  341.     OSErr    retVal;
  342.  
  343.     /* poll the outgoing pipe first, so that error messages are handled first */
  344.     if (pipeOut.session != pipeNoSession) {
  345.         *pip = &pipeOut;
  346.         retVal = pipeOut.readPB.ioResult;
  347.         }
  348.     else {
  349.         retVal = pipePpcNotDone;
  350.         }
  351.  
  352.     /* if the output pipe is not there or does not hold any message, poll the input pipe */
  353.     if (retVal == pipePpcNotDone  &&  pipeIn.session != pipeNoSession) {
  354.         *pip = &pipeIn;
  355.         retVal = pipeIn.readPB.ioResult;
  356.         }
  357.  
  358.     /* check whether one of the two PPC Reads has completed successfully */
  359.     if (retVal == noErr) {
  360.  
  361.         /* A PPC read was successful. Return the message. */
  362.         *userData = (**pip).readPB.userData;
  363.         StringCopy_M(buff, (**pip).readBuff);
  364.  
  365.         /* start a new PPC read */
  366.         retVal = PPCRead(&(**pip).readPB, true);
  367.  
  368.         }
  369.     else if (retVal == pipePpcNotDone) {
  370.  
  371.         /* Nothing this time. Check whether the user wants to abort the command. */
  372.         if (PipeCheckQuit() == true) retVal = userCanceledErr;
  373.         }
  374.  
  375.      return (retVal);
  376.      } /* PipePollOnce */
  377.  
  378. /******************************************************************************** PipeReportError */
  379. notMDOS void PipeReportError(
  380.                     OSErr            errCode,        /* --> error code */
  381.                     unsigned char    *errInClear        /* --> error message in clear */
  382.                     ) {
  383.     OSErr            err;
  384.     long            userData;
  385.     unsigned char    buff[pipeSize];
  386.     pipe_t            *pip;
  387.  
  388.     /* only attempt to send the message to MacDOS if the incoming pipe is open */
  389.     if (pipeIn.session != pipeNoSession) {
  390.  
  391.         /* prepare the message to report the error to MacDOS */
  392.         PipeEncErrUserData_M(errCode, userData, buff);
  393.         if (errInClear != nil  &&  errInClear[0] > 0) {
  394.             if ((short)errInClear[0] + (short)buff[0] >= pipeSize)
  395.                 errInClear[0] = pipeSize - buff[0] - 1;
  396.             StringAppend_M(buff, errInClear);
  397.             }
  398.  
  399.         /* send the error message to the incoming pipe */
  400.         err = PipeStartWrite(&pipeIn, userData, buff);
  401.         if (err == noErr) {
  402.  
  403.             /* keep polling the incoming pipe (while ignoring all messages) until it fails */
  404.             do {
  405.                 err = pipeIn.readPB.ioResult;
  406.                 if (err == noErr) {
  407.                     err = PPCRead(&pipeIn.readPB, true);
  408.                     if (err == noErr) err = pipeIn.readPB.ioResult;
  409.                     }
  410.                 if (err == pipePpcNotDone  &&  PipeCheckQuit() == true)
  411.                     err = userCanceledErr;
  412.                 } while (err == pipePpcNotDone);
  413.             }
  414.         }
  415.  
  416.     /* close the PPC port and quit */
  417.     PipeCleanUp();
  418.     ExitToShell();
  419.  
  420.     } /* PipeReportError */
  421.  
  422. /*********************************************************************************** PipeSendData */
  423. void PipeSendData(
  424.                 unsigned char    *buff        /* --> pointer to the message buffer (a P-string) */
  425.                 ) {
  426.     OSErr    err;
  427.     long    userData;
  428.  
  429.     /* encode the userData field of the message */
  430.     PipeEncUserData_M(nextFilterID, pipeDataMess, userData);
  431.  
  432.     /* send the message to the outgoing pipe */
  433.     err = PipeStartWrite(&pipeOut, userData, buff);
  434.     if (err != noErr) {
  435.         PipeReportError(err, nil);
  436.  
  437.         /* you will never come back here, because PipeReportError never returns */
  438.         }
  439.     } /* PipeSendData */
  440.  
  441. /*************************************************************************** PipeStartNextSession */
  442. onlyMDOS OSErr PipeStartNextSession(
  443.                         unsigned char    *portName,    /* --> port name of the next filter */
  444.                         unsigned char    id            /* --> filter ID */
  445.                         ) {
  446.     OSErr            retVal;
  447.     PPCStartPBRec    startPB;
  448.     PPCPortRec        portRec;
  449.     Boolean            userCancelled;
  450.  
  451.     /* initialise the port name structure with the name of the next filter */
  452.     portRec.nameScript    = *(short *)*(GetString(pipeScriptStrNum));
  453.     StringCopy_M(portRec.name, portName);
  454.     portRec.portKindSelector = ppcByString;
  455.     StringCopy_M(portRec.u.portTypeStr, pipeType);
  456.  
  457.     /* initialise the parameter block to start the session */
  458.     startPB.ioCompletion = nil;
  459.     startPB.portRefNum = port;
  460.     startPB.serviceType = ppcServiceRealTime;
  461.     startPB.resFlag = 0;
  462.     startPB.portName = &portRec;
  463.     startPB.locationName = nil;
  464.     startPB.userRefNum = 0;
  465.     startPB.userData = id;
  466.  
  467.     /* try to start the session until successful or until the user cancels */
  468.     do {
  469.         retVal = PPCStart(&startPB, false);
  470.         if (retVal != noErr  &&  PipeCheckQuit() == true) retVal = userCanceledErr;
  471.         } while (retVal != noErr  &&  retVal != userCanceledErr);
  472.  
  473.     /* if it was ok, save the session reference number */
  474.     if (retVal == noErr) {
  475.         pipeOut.session = startPB.sessRefNum;
  476.         retVal = StartOneSession(&pipeOut);
  477.         }
  478.  
  479.     return (retVal);
  480.     } /* PipeStartNextSession */
  481.  
  482. /********************************************************************************* PipeStartWrite */
  483. onlyMDOS OSErr PipeStartWrite(pipe_t *pip, long userData, unsigned char *buff) {
  484.     OSErr            retVal;
  485.     Boolean            userCancelled;
  486.     PPCWritePBRec    *writePB;
  487.  
  488.     writePB = &pip->writePB;
  489.  
  490.     /*
  491.      *    Before writing the message to the pipe, check that there is no previous write operation
  492.      *    outstanding. If there is one, wait that it completes before proceeding.
  493.      */
  494.     PipeWaitOnCond_M(writePB->ioResult == pipePpcNotDone, userCancelled);
  495.     if (userCancelled) {
  496.         retVal = userCanceledErr;
  497.         }
  498.     else {
  499.  
  500.         /* complete the parameter block (partially initialised in PipeStartOne */
  501.         StringCopy_M(pip->writeBuff, buff);
  502.         writePB->bufferLength = (Size)buff[0] + 1;
  503.         writePB->userData = userData;
  504.  
  505.         /* start the asynchronous write */
  506.         retVal = PPCWrite(writePB, true);
  507.         }
  508.  
  509.     return (retVal);
  510.     } /* PipeStartWrite */
  511.  
  512. /*----------------------------------------------------------------------------------- ProcessMess */
  513. static OSErr ProcessMess(pipeMessType_t messType, unsigned char *buff) {
  514.     OSErr            retVal;
  515.  
  516.     switch (messType) {
  517.  
  518.         case pipeParmMess:            /* Your parameters. Process them. */
  519.             if (processParms == nil)
  520.                 retVal = noErr;
  521.             else
  522.                 retVal = (*processParms)(buff);
  523.             break;
  524.  
  525.         case pipeNextMess:            /* Port name of the next filter. Start a session with it. */
  526.             nextFilterID = buff[buff[0]];
  527.             buff[0]--;
  528.             retVal = PipeStartNextSession(buff, nextFilterID);
  529.             break;
  530.  
  531.         case pipeDataMess:            /* This data is for your filter. Do your filtering. */
  532.             retVal = pipeReturnE;
  533.             break;
  534.  
  535.         default:
  536.             retVal = pipeUnknownMessErr;
  537.             break;
  538.         }
  539.  
  540.     return (retVal);
  541.     } /* ProcessMess */
  542.  
  543. /*------------------------------------------------------------------------------- StartOneSession */
  544. static OSErr StartOneSession(
  545.                 pipe_t        *p        /* --> pipe structure */
  546.                 ) {
  547.     OSErr            retVal;
  548.     PPCReadPBRec    *readPB;
  549.     PPCWritePBRec    *writePB;
  550.  
  551.     /* prepare to write to the pipe */
  552.     writePB = &p->writePB;
  553.     writePB->ioCompletion = nil;
  554.     writePB->sessRefNum = p->session;
  555.     writePB->ioResult = noErr;
  556.     writePB->bufferPtr = (Ptr)p->writeBuff;
  557.     writePB->more = false;
  558.  
  559.     /* do the initial polling of the pipe */
  560.     readPB = &p->readPB;
  561.     readPB->ioCompletion = nil;
  562.     readPB->sessRefNum = p->session;
  563.     readPB->bufferLength = (Size)pipeSize;
  564.     readPB->bufferPtr = (Ptr)p->readBuff;
  565.     retVal = PPCRead(readPB, true);
  566.  
  567.     return (retVal);
  568.     } /* StartOneSession */
  569.